package cn.chenxing.module.analysis.dbscan;

import cn.chenxing.domain.dbscan.Point;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description DBSCAN分析算法
 * @Author maogen.ymg
 * @Date 2020/4/17 21:38
 */
public class DbScan {
    /**
     * eps
     */
    private double eps;
    /**
     * minPts
     */
    private int minPts;

    /**
     * 构造函数
     * @param minPts 邻域内最小点数
     * @param eps 邻域半径
     */
    public DbScan(int minPts, double eps) {
        this.minPts = minPts;
        this.eps = eps;
    }

    /**
     * DbScan分析处理
     * @param points 待处理的点
     */
    public void process(List<Point> points) {
        // 簇
        int cluster = 1;

        for(Point p : points) {
            // 选择一个未访问的点
            if (!p.isVisited()) {
                // 设置已被访问
                p.setVisited(true);
                // 邻接点集
                List<Point> neighborPoints = getNeighborPoints(p, points);
                // 邻接点少于minPts的为噪音点noised
                if (neighborPoints.size() < minPts) {
                    p.setNoised(true);
                } else {
                    // 核心点，加入簇中
                    p.setCluster(cluster);

                    for (int i = 0; i < neighborPoints.size(); i++) {
                        Point neighborPoint = neighborPoints.get(i);
                        // 其他未访问的点有机会加入簇中
                        if (!neighborPoint.isVisited()) {
                            // 设置访问
                            neighborPoint.setVisited(true);
                            // 邻接点的邻接点
                            List<Point> neighborPoints1Points = getNeighborPoints(neighborPoint, points);
                            // 大于minPts的点加入
                            if (neighborPoints1Points.size() >= minPts) {
                                for (Point pp : neighborPoints1Points){
                                    if (!neighborPoints.contains(pp)){
                                        neighborPoints.add(pp);
                                    }
                                }
                            }
                        }//if
                        // 不属于其他簇的点加入
                        if (neighborPoint.getCluster() == 0) {
                            neighborPoint.setCluster(cluster);
                            // 若为噪音点则改为非噪音
                            if (neighborPoint.isNoised()) {
                                neighborPoint.setNoised(false);
                            }
                        }
                    }//for
                    cluster++;
                }//else
            }//if
        }
    }

    /**
     * 获取邻点集
     * @param centerPoint 中心点
     * @param points 数据点集
     * @return 邻点集
     */
    private List<Point> getNeighborPoints(Point centerPoint, List<Point> points) {
        List<Point> neighborPoints = new ArrayList<>();

        for(Point p:points) {
            double distance = centerPoint.getDistance(p);
            // 距离与半径
            if (distance <= eps) {
                neighborPoints.add(p);
            }
        }
        return neighborPoints;
    }

}
